package etcdv3

import (
	"context"
	"sync"
	"time"

	"github.com/coreos/etcd/clientv3"

	"nggs/etcd"
)

const (
	dialTimeout    = 3 * time.Second
	requestTimeout = 3 * time.Second
)

var C = New()

type Client struct {
	*clientv3.Client
}

func New() *Client {
	return &Client{}
}

func (c *Client) Init(endpoint string) (err error) {
	c.Client, err = clientv3.New(clientv3.Config{
		Endpoints:   []string{endpoint},
		DialTimeout: dialTimeout,
	})
	if err != nil {
		return
	}
	return
}

func (c *Client) GrantLease(timeout int64) (clientv3.LeaseID, error) {
	resp, err := c.Grant(context.TODO(), timeout)
	if err != nil {
		return 0, err
	}
	return resp.ID, nil
}

func (c *Client) KeepAliveLease(leaseID clientv3.LeaseID) (<-chan *clientv3.LeaseKeepAliveResponse, error) {
	rch, err := c.KeepAlive(context.TODO(), leaseID)
	return rch, err
}

func (c *Client) put(key string, value string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) {
	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	resp, err := c.Client.Put(ctx, key, value, opts...)
	cancel()
	return resp, err
}

func (c *Client) Put(key string, value string) error {
	_, err := c.put(key, value)
	return err
}

func (c *Client) PutWithLease(key string, value string, leaseID clientv3.LeaseID) error {
	_, err := c.put(key, value, clientv3.WithLease(leaseID))
	return err
}

func (c *Client) get(key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	resp, err := c.Client.Get(ctx, key, opts...)
	cancel()
	return resp, err
}

func (c *Client) Get(key string) ([]byte, error) {
	resp, err := c.get(key)
	if err != nil {
		return nil, err
	}
	if len(resp.Kvs) > 0 {
		return resp.Kvs[0].Value, nil
	}
	return nil, etcd.ErrKeyNotFound
}

func (c *Client) GetString(key string) (string, error) {
	resp, err := c.get(key)
	if err != nil {
		return "", err
	}
	if len(resp.Kvs) > 0 {
		return string(resp.Kvs[0].Value), nil
	}
	return "", etcd.ErrKeyNotFound
}

func (c *Client) GetStrings(prefix string) (map[string]string, error) {
	kvs := map[string]string{}
	resp, err := c.get(prefix, clientv3.WithPrefix())
	if err != nil {
		return kvs, err
	}
	for _, kv := range resp.Kvs {
		kvs[string(kv.Key)] = string(kv.Value)
	}
	return kvs, nil
}

func (c *Client) delete_(key string, opts ...clientv3.OpOption) (*clientv3.DeleteResponse, error) {
	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	resp, err := c.Client.Delete(ctx, key, opts...)
	cancel()
	return resp, err
}

func (c *Client) Delete(key string) (int64, error) {
	resp, err := c.delete_(key)
	if err != nil {
		return 0, err
	}
	return resp.Deleted, err
}

func (c *Client) DeleteWithPrefix(prefix string) (int64, error) {
	resp, err := c.delete_(prefix, clientv3.WithPrefix())
	if err != nil {
		return 0, err
	}
	return resp.Deleted, nil
}

func (c *Client) Compact(rev int64, opts ...clientv3.CompactOption) (*clientv3.CompactResponse, error) {
	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	resp, err := c.Client.Compact(ctx, rev, opts...)
	cancel()
	return resp, err
}

func (c *Client) do(op clientv3.Op) (clientv3.OpResponse, error) {
	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	resp, err := c.Client.Do(ctx, op)
	cancel()
	return resp, err
}

func (c *Client) txn(ctx context.Context) clientv3.Txn {
	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	t := c.Client.Txn(ctx)
	cancel()
	return t
}

func (c *Client) watch(key string, opts ...clientv3.OpOption) clientv3.WatchChan {
	rch := c.Client.Watch(context.Background(), key, opts...)
	return rch
}

func (c *Client) Watch(key string) clientv3.WatchChan {
	return c.watch(key)
}

func (c *Client) WatchWithPrefix(prefix string) clientv3.WatchChan {
	return c.watch(prefix, clientv3.WithPrefix())
}

type WatchCallback func(ev *clientv3.Event)

func WatchLoop(rch clientv3.WatchChan, cb WatchCallback, wg *sync.WaitGroup) {
	if wg != nil {
		wg.Done()
	}
	for {
		select {
		case resp := <-rch:
			for _, ev := range resp.Events {
				cb(ev)
			}
		}
	}
}
